const Parser = @This();
const std = @import("std");
const Allocator = std.mem.Allocator;
const File = std.fs.File;

pub const SymbolMap = std.StringHashMap(usize);
pub const Statements = std.ArrayList([]const u8);

arena: std.heap.ArenaAllocator,
statements: Statements,
symtable: SymbolMap,

pub fn parse(allocator: Allocator, asmfile: File) !Parser {
    var arena: std.heap.ArenaAllocator = .init(allocator);
    var list: Statements = .init(allocator);
    var symtable: SymbolMap = .init(allocator);

    var pc: usize = 0;
    while (try nextLine(arena.allocator(), asmfile)) |line| {
        const ins = try trimLine(&symtable, line, &pc);
        if (ins != null) {
            try list.append(ins.?);
        }
    }

    for (builtin_symbols) |pair| {
        try symtable.put(pair[0], pair[1]);
    }

    return .{
        .arena = arena,
        .statements = list,
        .symtable = symtable,
    };
}

pub fn deinit(self: *Parser) void {
    self.arena.deinit();
    self.statements.deinit();
    self.symtable.deinit();
}

fn nextLine(allocator: Allocator, asmfile: File) !?[]const u8 {
    const buffer = try allocator.alloc(u8, 1024);
    const reader = asmfile.reader();

    // 显式指定变量类型为 []const u8，把 []u8 赋值给它
    // 因为下面的 trimRight 返回 []const u8，不能把 []const u8 赋值给 []u8
    const line: []const u8 = (try reader.readUntilDelimiterOrEof(buffer, '\n')) orelse return null;
    return std.mem.trimRight(u8, line, "\r");
}

fn trimLine(symtable: *SymbolMap, oneline: []const u8, pc: *usize) !?[]const u8 {
    var line = std.mem.trimLeft(u8, oneline, " \t");
    if (line.len == 0) return null;

    return switch (line[0]) {
        '/' => null,
        '(' => blk: {
            const sym = line[1..std.mem.indexOfScalar(u8, line, ')').?];
            try symtable.put(sym, pc.*);
            break :blk null;
        },
        else => blk: {
            pc.* += 1;
            break :blk line;
        },
    };
}

const builtin_symbols = [_]struct { []const u8, usize }{
    .{ "R0", 0 },
    .{ "R1", 1 },
    .{ "R2", 2 },
    .{ "R3", 3 },
    .{ "R4", 4 },
    .{ "R5", 5 },
    .{ "R6", 6 },
    .{ "R7", 7 },
    .{ "R8", 8 },
    .{ "R9", 9 },
    .{ "R10", 10 },
    .{ "R11", 11 },
    .{ "R12", 12 },
    .{ "R13", 13 },
    .{ "R14", 14 },
    .{ "R15", 15 },
    .{ "SP", 0 },
    .{ "LCL", 1 },
    .{ "ARG", 2 },
    .{ "THIS", 3 },
    .{ "THAT", 4 },
    .{ "SCREEN", 0x4000 },
    .{ "KBD", 0x6000 },
};
